package lol.niconico.libtest.data

import androidx.paging.*
import androidx.room.withTransaction
import com.blankj.utilcode.util.NetworkUtils
import kotlinx.coroutines.flow.Flow
import lol.niconico.libtest.data.db.AppDatabase
import lol.niconico.libtest.entity.RemoteKeysEntity
import lol.niconico.libtest.entity.UserEntity
import lol.niconico.libtest.http.GithubService
import retrofit2.HttpException
import java.io.IOException
import javax.inject.Inject

/**
 * Created by ZhangQianqian on 2020/7/15  15:25.
 * email 415692689@qq.com
 */
class GitHubRepository @Inject constructor(
    val gitHubApi: GithubService,
    val db: AppDatabase
) {

    @OptIn(ExperimentalPagingApi::class)
    fun getUserListByNetWithDb(): Flow<PagingData<UserEntity>> {
        return Pager(
            config = PagingConfig(30),
            remoteMediator = PokemonRemoteMediator(gitHubApi, db)
        ) {
            db.userDao().getUser()
        }.flow
    }

    fun getUserListByNet(): Flow<PagingData<UserEntity>> {
        return Pager(config = PagingConfig(30)) {
            GitHubItemPagingSource(gitHubApi)
        }.flow
    }
}

@ExperimentalPagingApi
class PokemonRemoteMediator(
    val networkService: GithubService,
    val db: AppDatabase
) : RemoteMediator<Int, UserEntity>() {
    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, UserEntity>
    ): MediatorResult {
        try {
            val remoteUser = "remoteUser"
            val userDao = db.userDao()
            val remoteKeysDao = db.remoteKeysDao()

            val pageKey = when (loadType) {
                // 首次访问 或者调用 PagingDataAdapter.refresh()
                LoadType.REFRESH -> null
                // 在当前加载的数据集的开头加载数据时
                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
                // 在当前数据集末尾添加数据
                LoadType.APPEND -> {
                    val remoteKey = db.withTransaction {
                        db.remoteKeysDao().getRemoteKeys(remoteUser)
                    }
                    if (remoteKey?.nextKey == null) {
                        return MediatorResult.Success(endOfPaginationReached = true)
                    }
                    remoteKey.nextKey
                }
            }

            if (!NetworkUtils.isConnected()) {
                // 无网络加载本地数据
                return MediatorResult.Success(endOfPaginationReached = true)
            }

            val page = pageKey ?: 0
            val result = networkService.getGithubAccount(
                state.config.pageSize,
                page * state.config.pageSize
            )

            val endOfPaginationReached = result.isEmpty()

            db.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    remoteKeysDao.clearRemoteKeys(remoteUser)
                    userDao.clearUser()
                }
                val nextKey = if (endOfPaginationReached) null else page + 1
                val entity = RemoteKeysEntity(
                    remoteName = remoteUser,
                    nextKey = nextKey
                )
                remoteKeysDao.insertAll(entity)
                userDao.insertUser(result)
            }
            return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
        } catch (e: IOException) {
            return MediatorResult.Error(e)
        } catch (e: HttpException) {
            return MediatorResult.Error(e)
        }
    }
}


class GitHubItemPagingSource(
    private val api: GithubService
) : PagingSource<Int, UserEntity>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UserEntity> {

        return try {
            // key 相当于 id
            val key = params.key ?: 0
            // 获取网络数据
            val items = api.getGithubAccount(key, params.loadSize)
            // 请求失败或者出现异常，会跳转到 case 语句返回 LoadResult.Error(e)
            // 请求成功，构造一个 LoadResult.Page 返回
            LoadResult.Page(
                data = items, // 返回获取到的数据
                prevKey = null, // 上一页，设置为空就没有上一页的效果，这需要注意的是，如果是第一页需要返回 null，否则会出现多次请求
                nextKey = items.lastOrNull()?.id// 下一页，设置为空就没有加载更多效果，需要注意的是，如果是最后一页返回 null
            )
        } catch (e: Exception) {
            e.printStackTrace()
            LoadResult.Error(e)
        }
    }
}